#include "main.h"

bool reader::get_str()
{
    while (true) {
	while (isspace(str[index]))
	    ++index;
	if (str[index] && str[index] != ';')
	    return true;
	str = env->get_input()->read();
	index = 0;
	if (!str[0])
	    return false;
    }
}

bool reader::isnum()
{
    if (isdigit(str[index]))
	return true;
    if (str[index] == '+' || str[index] == '-')
	if (isdigit(str[index + 1]))
	    return true;
    return false;
}

bool reader::isesc()
{
    switch (str[index]) {
    case ' ':
    case ';':
    case '(':
    case ')':
    case '.':
    case '\'':
    case '\"':
    case ',':
    case '\\':
	return true;
    default:
	return false;
    }
}

std::string reader::get_name()
{
    std::string name;

    while (true) {
	if (str[index] == '\\')
	    ++index;
	else if (isspace(str[index]) || isesc() || !str[index]) {
	    if (str[index] == ';')
		str[index] = 0;
	    return name;
	}
	if (iscntrl(str[index]))
	    throw Error(new error(error::ctrlin));
	name += str[index++];
    }
}

Object reader::read_num()
{
    std::string buf;

    if (str[index] == '+' || str[index] == '-')
	buf += str[index++];
    while (isdigit(str[index]))
	buf += str[index++];
    if (str[index] == '.')
	buf += str[index++];
    while (isdigit(str[index]))
	buf += str[index++];
    if (str[index] == 'e' || str[index] == 'E') {
	buf += str[index++];
	if (str[index] == '+' || str[index] == '-')
	    buf += str[index];
	while (isdigit(str[index]))
	    buf += str[index++];
    }
    double d;

    d = atof(buf.c_str());
    return Number(new number(d));
}

Object reader::read_sym()
{
    std::string name;
    Object sym;

    name = get_name();
    try {
	sym = env->get_value(name);
    } catch (...) {
	sym = Symbol(new symbol(name));
	env->bind(sym, sym);
    }
    return env->get_value_read(name);
}

Object reader::escape()
{
    switch (str[index]) {
    case '(':
	return read_list();
    case '\\':
	return read_sym();
    case '\"':
	return read_str();
    case '\'':
	return read_decomp(env->get_value("quote"));
    case ',':
	return read_decomp(env->get_value("eval"));
    default:
	throw Error(new error(error::syntax));
    }
}

Object reader::read_str()
{
    std::string s;

    s += str[index++];
    while (1) {
	if (str[index] == '\"') {
	    s += str[index++];
	    return String(new string(s));
	}
	if (!str[index]) {
	    if (!get_str())
		throw Error(new error(error::eof));
	    continue;
	}
	if (iscntrl(str[index]))
	    throw Error(new error(error::ctrlin));
	s += str[index++];
    }
}

Object reader::read_list()
{
    Object obj, o1, o2;

    ++index;
    if (!get_str())
	throw Error(new error(error::eof));
    if (str[index] == ')')
	return env->get_value("false");
    if (str[index] == '.')
	throw Error(new error(error::syntax));
    obj = o1 = Cons(new cons(env));
    o1->set_car(get());
    if (!get_str())
	throw Error(new error(error::eof));
    while (str[index] != ')') {
	if (str[index] == '.') {
	    ++index;
	    if (!get_str())
		throw Error(new error(error::eof));
	    if (str[index] == ')')
		throw Error(new error(error::syntax));
	    o1->set_cdr(get());
	    break;
	}
	o2 = Cons(new cons(env));
	o1->set_cdr(o2);
	o2->set_car(get());
	o1 = o2;
	if (!get_str())
	    throw Error(new error(error::eof));
    }
    ++index;
    return obj;
}

Object reader::read_decomp(Object func)
{
    Object o(new cons(env)), o2(new cons(env));

    o->set_car(func);
    o->set_cdr(o2);
    ++index;
    o2->set_car(get());
    return o;
}

Object reader::get()
{
    if (!get_str())
	return env->get_value("eof");
    else if (isnum())
	return read_num();
    else if (isesc())
	return escape();
    else if (!iscntrl(str[index]))
	return read_sym();
    throw Error(new error(error::ctrlin));
}
